/*
	TODO UPGRADE vers LIBPERSEUS
*/

#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>

#include "perseus.h"

#define MY_FREE(p) \
        free((p)); \
        (p) = NULL;

int nothing(FILE* file, char * format, ...)
{
    file = file;
    format = format;

    return 1;
}
void print_table(char * table, unsigned long size);


/*
static inline void MY_FREE( void *p)
{
        free(p);
        p=NULL;
}*/

/*****************************************************************************************************/
/*****************************************************************************************************/
/*****************************************************************************************************/
/*****************************************************************************************************/
/*****************************************************************************************************/
/*****************************************************************************************************/
/*                                    String transformation                                          */


char *
int_to_char(
            unsigned long * aTab,
            unsigned long long aLength,
            unsigned int k,
            unsigned int N,
            unsigned long *size)
{
  unsigned long long i=0, n=0;
  unsigned long j=0;
  unsigned char r=0;
  char * data=NULL;

  
  aLength=aLength-N;
  *size=(unsigned long) ceil((float) (aLength*k/8.0));
  data = (char *) malloc(((*size)+1)*sizeof(char));
  if (data==NULL)
  {
    return NULL;
  }
  memset(data,0,(*size)+1);

  if (k==8) {
      for(i=0; i<*size;i++)
        data[i]=aTab[i];
  }
  else
    {
      i=0;
      j=0;
      n=0;
      while(1)
        {

          for(r=0;r<8;r++) {
              if(i==k) {
                  if(++n==aLength) {
                      data[j]='\0';
                      *size=j;
                      return data;
                  }
                  i=0;
              }
              data[j] |= ((aTab[n]>>(i)) &0x1)<<r;
              i++;
          }
          j++;
        }
    }
  data[*size]='\0';
  return data;
}

unsigned char
hex_to_Binchar(
        const char * aDataHex,
        unsigned long aLength,
        char ** aDataBin,
        unsigned long long * aDataBinLength)
{
    unsigned long i = 0;
    unsigned char j = 0;
    unsigned char ret = 0;
    unsigned char tmp = 0;
    unsigned char rest= 0 ;

    *aDataBinLength=0;
    if (aLength <= 1)
    {
        ret = 0;
        *aDataBin = NULL;
        goto end;;
    }
    
    rest = aDataHex[0] -'0';
    
    *aDataBin=(char *) malloc(sizeof(char)*((aLength-1)*4)+1);
    if (*aDataBin == NULL)
    {
        ret =0;
        goto end;
    }

    for(i=1;i<aLength-1;i++) {
        if ('0' <= aDataHex[i] && aDataHex[i]<= '9') {
            tmp=aDataHex[i]-'0';
        } else if ( 'A'<= aDataHex[i] && aDataHex[i]<= 'F') {
            tmp=aDataHex[i] - 'A'+ 10;
        }
        for(j=0;j<4;j++) 
            (*aDataBin)[(*aDataBinLength)++]= (tmp>>(3-j)) &0x1;
    }

    /*Last Hex symbol */
    if ('0' <= aDataHex[aLength-1] && aDataHex[aLength-1]<= '9') {
        tmp=aDataHex[aLength-1]-'0';
    } else if ( 'A'<= aDataHex[aLength-1] && aDataHex[aLength-1]<= 'F') {
        tmp=aDataHex[aLength-1] - 'A'+ 10;
    }
    for(j=0;j<(4-rest);j++) 
            (*aDataBin)[(*aDataBinLength)++]= (tmp>>(3-j)) &0x1;
    ret = 1;
    (*aDataBin)[(*aDataBinLength)]='\0';

end:
    return ret;
}

unsigned char
char_to_bin(
        const char * aData,
        unsigned long aLength,
        unsigned char aK,
        unsigned char aN,
        char ** aDataBin,
        unsigned long long * aNewLength
        )
{
    unsigned char Length_car=8;
    unsigned char j = 0;
    unsigned char ret = 0;
    unsigned long long i;
    unsigned long long k=0;
    unsigned int l = 0;
    unsigned int bour= (aK - ((aLength*Length_car) % aK)) % aK;
    bour= bour +(aK*aN);
    *aDataBin= (char *) malloc(((aLength*Length_car)+bour)*sizeof(char) );
    if (aDataBin == NULL)
    {
        ret = 0;
        goto end;
    }
    
    for(i=0;i<aLength;i++) {
        for(j=0;j<Length_car;j++)
            (*aDataBin)[k++]= ((unsigned char)aData[i] >> j) &0x01;
    }
    for(l=0;l<bour;l++)
        (*aDataBin)[k++]=0;
    *aNewLength=k;
    ret = 1;
end :
    return ret;
}

/*hex to char*/
/* return -1 error*/
char 
htoc(unsigned char hex)
{
    if ( /*hex>=0 &&*/ hex<=9)
        return hex+'0';
    if ( hex>9 && hex<=15)
        return hex+'A'-10;
    return -1;
}

char **
bin_to_charHex(char * aBinData,
               unsigned long long aLength,
               char ** aHexData,
               unsigned long * aHexDataLength)
{
    unsigned long i,k;
    unsigned char j=0,tmp=0;
   
   /* Alength /4 +1 == longueur des données en hexa
    * +1 pour le premier chiffre de padding
    * +1 pour la fin de chaine
    */
   /* (*aHexDataLength)=0;*/
    *aHexData = (char *) malloc(((aLength/4)+1+1+1)*sizeof(char));
    if (*aHexData == NULL)
    {
        goto end;
    }
    memset(*aHexData,0,((aLength/4)+1+1+1));
    
    k=1;
    for(i=0;i<aLength;i++) {
        tmp<<=1;
        tmp|=aBinData[i]&1;
        j++;
        if (j==4) {
            j=0;
            (*aHexData)[k]=htoc(tmp);
            k++;
            tmp=0;
        }   
    }

    if (j == 0) {
        (*aHexData)[0]='0';
    }
    else {
        (*aHexData)[0]='0'+(4-j);
        while (j<4) {
            j++;
            tmp<<=1;
        }
        (*aHexData)[k]=htoc(tmp);
        k++;
    }
    (*aHexData)[k]='\0';
    (*aHexDataLength)=k;
end:
    return aHexData;
}


/*****************************************************************************************************/
/*****************************************************************************************************/
/*****************************************************************************************************/
/*****************************************************************************************************/
/*****************************************************************************************************/
/*****************************************************************************************************/
/*                                    Noise function                                                 */

unsigned char noise(unsigned long long aReg, unsigned char aNumLine)
{
   unsigned long long index[10] = { 0x8A914C0804621A14ULL ,
      0x914A014085241543ULL ,
      0xA04281A49A023510ULL,
      0x104850915C10982CULL  ,
      0x4485A04212305138ULL ,
      0x31A0482C81234508ULL ,
      0x19024D0410C123A1ULL ,
      0x84A21115C6140105ULL ,
      0x4502618C13156840ULL ,
      0x3241C2034898C141ULL};
   
   /* 0 <= I <= 63 */
    unsigned char I = (aReg>>33) & 0x3F;
    return ((index[aNumLine]>>I) & 0x1);
}

unsigned char
sumbit(unsigned long long w)
{
    w ^= w>>32;
    w ^= w>>16;
    w ^= w>>8;
    w ^= w>>4;
    w ^= w>>2;
    w ^= w>>1;
    return (w & 0x1);
}

unsigned long long
_random(unsigned long long aPol, unsigned long long aReg)
{
    unsigned char rebouclage;
    unsigned char i;
    unsigned long long tmp;
    for(i=0;i<64; i++) {
        rebouclage = sumbit(aReg & aPol);
        aReg >>= 1;
        tmp = rebouclage;
        aReg |= tmp<<63;
    }
    return aReg;
}

unsigned char
addNoise(unsigned long long X0, unsigned char numLine,char * aData, unsigned long long aLength)
{
    unsigned long long reg=X0;
    unsigned long long P =  0x800000000000FFF9ULL;
    unsigned char Et;
    unsigned long long i=0,r=0;

    for(i=0;i<aLength;i++) {
        reg = _random(P,reg);
        Et = noise(reg,numLine);
        aData[i]^=Et;
        r++;
    }
    return 1;
}


/*****************************************************************************************************/
/*****************************************************************************************************/
/*****************************************************************************************************/
/*****************************************************************************************************/
/*****************************************************************************************************/
/*****************************************************************************************************/
/*                                    PCC function                                              */
unsigned int
pcc_getN(pcc * p)
{
    return p->mN;
}

unsigned int
pcc_getK(pcc * p)
{
    return p->mK;
}

unsigned int
pcc_getM(pcc * p)
{
    return p->mM;
}

unsigned long **
pcc_getPoly(pcc * p)
{
    return p->mPoly;
}

unsigned int
pcc_getMatWidth(pcc * p)
{
    return p->mMatWidth;
}

unsigned char *
pcc_getMatrix(pcc * p)
{
    return p->mMatrix;
}

unsigned char
pcc_getNumLine(pcc * p)
{
    return p->mNumLine;
}

unsigned long long 
pcc_getX0(pcc * p)
{
    return p->mX0;
}

void
compute(unsigned int mN,
        unsigned int mM,
        unsigned long * aPoly,
        unsigned char aBit,
        unsigned long aMemory,
        unsigned int *value)
{
    unsigned int i = 0;
    unsigned int j = 0;
    unsigned char pol = 0;
    unsigned int val = 0;

    for(i=0;i<mN;i++) {
        pol=aPoly[i]&1;
        val=pol&aBit;
        for(j=1;j<=mM;++j) {
            pol=(aPoly[i]>>j)&1;
            val^=pol&(aMemory>>(j-1));
        }
        *value<<=1;
        *value|=val;
    }
}


unsigned char
pcc_puncture(pcc *p,char * aStream, unsigned long long aLength,
            char ** aNewStream, unsigned long long *aNewLength)
{

    unsigned long long i = 0;
    unsigned char j=0;
    unsigned char ret = 0;
    *aNewLength=0;
    *aNewStream=(char *) malloc(aLength*sizeof(char));

    if (*aNewStream == NULL)
    {
        ret = 0;
        goto end;
    }

    /*
     * We don't puncture the last n  bit
     */
    for(i=0;i<aLength-1;i++) {
        if (j==(p->mMatWidth)*(p->mN)) {
            j=0;
        }
        if((p->mMatrix)[j]) {
            (*aNewStream)[*aNewLength]=aStream[i];
            (*aNewLength)++;
        }
        j++;
    }
   
    (*aNewStream)[*aNewLength]=aStream[aLength-1];
    (*aNewLength)++;
    ret = 1;
end:
    return ret;
}

unsigned char
pcc_unpuncture( pcc *p,char * aStream, unsigned long long aLength,
        char ** aNewStream, unsigned long long *aNewLength)
{
    unsigned long long i=0;
    unsigned char j=0;
    unsigned char ret=0;
    unsigned long long taille=(aLength+(aLength/(p->mN*p->mMatWidth-p->mMatDepth)+1)*p->mMatDepth);
    
    *aNewLength=0;
    *aNewStream=(char *)  malloc(taille*sizeof(char));
    if (*aNewStream == NULL)
    {
        ret = 0;
        goto end;
    }
    i=0;
    while(i<aLength) {
        if (j==p->mMatWidth*p->mN) {
            j=0;
        }
        if(! ((p->mMatrix)[j]) )
            (*aNewStream)[*aNewLength]=0xF;
        else {
            (*aNewStream)[*aNewLength]=aStream[i++];
        }
        (*aNewLength)++;
        j++;
    }
    ret = 1;
end:
   return ret;
}



/*****************************************************************************************************/
/*****************************************************************************************************/
/*****************************************************************************************************/
/*****************************************************************************************************/
/*****************************************************************************************************/
/*****************************************************************************************************/
/*                                    Position function                                              */

typedef struct position_t 
{
    unsigned int input;
    unsigned long* memory;
    struct position_t * previous;
} position;

position *
position_new(
        unsigned int input, position * previous)
{
    position * p = (position *) malloc(sizeof(position));
    if (p == NULL)
    {
        goto end;
    }
    p->input = input;
    p->previous = previous;
    p->memory = NULL;
end:
    return p;
}

unsigned char
position_copyMemory(position * p,unsigned long *aMemory, unsigned char aLength)
{
    unsigned char i=0;
    unsigned char ret=0;
    p->memory= (unsigned long *) malloc(aLength*sizeof(unsigned long));
    if (p->memory == NULL) {
        ret = 0;
        goto end;
    }
    for(i=0;i<aLength;i++)
    {
        p->memory[i]=aMemory[i];
    }
    ret = 1;
end:
    return ret;
}

void
position_delete(position *p)
{
    if (p != NULL)
    {
        MY_FREE(p->memory);
        p->previous=0;
    }
}

void
position_setPrevious(position * p,position * aPrevious)
{
    p->previous=aPrevious;
}

unsigned long * 
position_getMemory(position *p)
{
    return p->memory;
}

position *
position_getPrevious(position *p)
{
    return p->previous;
}

unsigned int
position_getInput(position *p)
{
    return p->input;
}


/*****************************************************************************************************/
/*****************************************************************************************************/
/*****************************************************************************************************/
/*****************************************************************************************************/
/*****************************************************************************************************/
/*****************************************************************************************************/
/*                                    Noded function                                              */


typedef struct noded_t
{
    position ** Block;
    char metric;
    unsigned long positionNumber;
} noded;

noded *
noded_init(char metric, unsigned long positionNumber, position ** block)
{
    noded *n = (noded *) malloc(1*sizeof(noded));  
    if (n == NULL)
    {
        return NULL;
    }
    n->metric = metric;
    n->positionNumber = positionNumber;
    n->Block = block;
    return n;
}


unsigned char
noded_addPosition(noded *n,char aMetric,unsigned int aInput,unsigned long *aMemory,
        unsigned char aMemoryLength, position *aPrevious)
{
    unsigned char ret = 0;
    position * p; 

    n->Block= (position **) realloc(n->Block,sizeof(position*)*(n->positionNumber+1));
    if (n->Block == NULL)
    {
        ret = 0;
        goto end;
    }
    p = position_new(aInput, aPrevious);
    if (p == NULL)
    {
        /* 
         * TODO : ALLOC FREE
         */
        ret = 0;
        goto end;
    }
    ret = position_copyMemory(p,aMemory, aMemoryLength); 
    if (ret == 0)
    {
        /* 
         * TODO : ALLOC FREE
         */
        ret = 0;
        goto end;
    }
    n->positionNumber++;
    n->Block[n->positionNumber-1]=p;
    n->metric=aMetric;
    ret=1;
end:
    return ret;
}

unsigned char
noded_replaceBlock(noded *n,char aMetric,unsigned int aInput,unsigned long *aMemory,
        unsigned char aMemoryLength, position *aPrevious)
{
    unsigned char ret = 0;
    unsigned long i;
    position * p;
    for(i=0;i<n->positionNumber;i++)
    {
        position_delete(n->Block[i]);
        MY_FREE(n->Block[i]);
    }
    MY_FREE(n->Block);
    n->positionNumber=1;
    n->Block= (position **) malloc(sizeof(position*));
    if (n->Block == NULL)
    {
        ret = 0;
        goto end;
    }
    p=position_new(aInput,aPrevious);
    if (p == NULL)
    {
        ret = 0;
        goto end;
    }
    ret = position_copyMemory(p,aMemory, aMemoryLength); 
    if (ret == 0)
    {
        ret = 0;
        goto end;
    }
    n->Block[n->positionNumber-1]=p;
    n->metric=aMetric;
    ret = 0;
end:
    return ret;
}
 
char noded_isEmpty(noded* n)
{
    return ((n->positionNumber) ?1 : 0);
}

void
noded_delete(noded * n)
{
    unsigned long i;
    if (n != NULL)
    {
        for(i=0;i<n->positionNumber;i++)
        { 
            position_delete(n->Block[i]);
            MY_FREE(n->Block[i]);
        }

        MY_FREE(n->Block);
    }
}

void
noded_setMetric(noded * n, char aMetric)
{
    n->metric = aMetric;
}

char  
noded_getMetric(noded *n)
{
    return n->metric;
}

unsigned long 
noded_getPositionNumber(noded *n)
{
    return n->positionNumber;
}

unsigned long * 
noded_getMemory(noded *n,unsigned long aPosition)
{
    return position_getMemory( n->Block[aPosition]);
}

position * 
noded_getAddressPosition(noded *n,unsigned long aPosition)
{
    return n->Block[aPosition]; 
}

/*****************************************************************************************************/
/*****************************************************************************************************/
/*****************************************************************************************************/
/*****************************************************************************************************/
/*****************************************************************************************************/
/*****************************************************************************************************/
/*                                    Treillis function                                              */
typedef struct viterbi_t
{
    noded *** treillis;
    unsigned long long width;
    unsigned int height;
    pcc *aCode;
    char * data;
    unsigned long long dataLength;
}viterbi;

viterbi *
viterbi_init(pcc *aPcc,char * aData, unsigned long long aLength)
{
    
    unsigned long long i;
    unsigned int j;
    
    viterbi * v = (viterbi *) malloc(sizeof(viterbi));

    if (v == NULL) 
    {
        v = NULL;
        goto end;
    }

    v->aCode = aPcc;
    v->data=aData;
    v->dataLength=aLength;
    v->height=1<<(pcc_getN(v->aCode));
    v->width=floor(v->dataLength/(pcc_getN(v->aCode)));
    v->treillis = (noded ***) malloc(sizeof(noded**)*(v->width+1));
    if (v->treillis == NULL)
    {
        goto end1;
    }
    
    for (i=0;i<(v->width+1);i++) 
    {
        v->treillis[i] = (noded **) malloc(v->height*sizeof(noded*));
    
        if (v->treillis[i] == NULL)
        {
            for (i=i-1;i>0;i--)
            {
                MY_FREE(v->treillis[i]);
            }
            MY_FREE(v->treillis[0]);
            goto end2;
        }
        for (j=0;j<v->height;j++)
        {
            v->treillis[i][j] = NULL;
        }
    }
    goto end;

end2:
    MY_FREE(v->treillis);
end1:
    MY_FREE(v);
end:
    return v;
}

void
viterbi_delete(viterbi * v)
{
    unsigned long long i = 0;
    unsigned int j = 0;
    for(i=0;i<v->width+1;i++)
    {
        for(j=0;j<v->height;j++)
        {
            if (v->treillis[i][j] != NULL)
                noded_delete(v->treillis[i][j]);
            MY_FREE(v->treillis[i][j]);
        }
        MY_FREE(v->treillis[i]);
    }
    MY_FREE(v->treillis);
}

void
viterbi_setMetric(viterbi *v,unsigned long long x,unsigned int y, char aMetric)
{
    noded_setMetric(v->treillis[x][y],aMetric);
}

char 
viterbi_getMetric(viterbi *v,unsigned long long x, unsigned int y)
{
    return noded_getMetric(v->treillis[x][y]);
}

char
viterbi_isEmpty(viterbi *v,unsigned long long x, unsigned int y)
{
    return (v->treillis[x][y]==NULL);
}

unsigned int
viterbi_NumberOfPosition(viterbi *v,unsigned long long x, unsigned int y)
{
    return noded_getPositionNumber(v->treillis[x][y]);
}

unsigned long *
viterbi_getMemory(viterbi *v, unsigned long long x, unsigned int y, unsigned int aPosition)
{
    return noded_getMemory(v->treillis[x][y],aPosition);
}

position *
viterbi_getAddressPosition(viterbi *v,unsigned long long x, unsigned int y, unsigned int aPosition)
{
    return noded_getAddressPosition(v->treillis[x][y],aPosition);
}

unsigned char
distance(const char * a, unsigned int b, unsigned char length, unsigned long long begin)
{
    unsigned char i, dist=0;
    for(i=0;i<length;i++) {

        if (a[begin] != 0xF) 
            dist+=(a[begin]&0x1) ^ (b>>(length-1-i) &0x1);
        begin++;
    }
    return dist;
}

unsigned char
viterbi_addPosition(viterbi *v,unsigned long long x, unsigned int y, 
        char aMetric,unsigned int aInput,
        unsigned long *aMemory, unsigned char aMemoryLength,
        position *aPrevious)
{
    unsigned char ret = 0;
    if (v->treillis[x][y] == NULL)
    {
        v->treillis[x][y] = noded_init(0,0,0);
        if (v->treillis[x][y] == NULL)
        {
            ret = 0;
            goto end;
        }
        ret = noded_addPosition(v->treillis[x][y],aMetric,aInput,aMemory,aMemoryLength, aPrevious);
        if (ret == 0)
        {
            /*
             * TODO : FREE ??
             */
            ret = 0;
            goto end;
        }
    } 
    else
    {
        if (viterbi_getMetric(v,x,y)>aMetric)
        {
            ret = noded_replaceBlock(v->treillis[x][y],aMetric,aInput,aMemory,aMemoryLength, aPrevious);
            if (ret == 0)
            {
                /*
                 * TODO : FREE ??
                 */
                ret = 0;
                goto end;
            }
        }
        else
        {
            if (viterbi_getMetric(v,x,y)==aMetric || viterbi_NumberOfPosition(v,x,y)==0) 
            {
                ret = noded_addPosition(v->treillis[x][y],aMetric,aInput,aMemory,aMemoryLength, aPrevious);
                if (ret == 0)
                {
                    /*
                     * TODO : FREE ??
                     */
                    ret = 0;
                    goto end;
                }
            }
            /*
             * else  do nothing
             */
        }
    }
end :
    return ret;
}

unsigned char 
viterbi_backward(viterbi *v,char ** aDataDecoded, unsigned long *dataLength )
{
    unsigned ret = 0;
    /*
     * Search the minus
     */
    char minMetric=-1, metric=0;
    position * tmp=NULL;
    unsigned long long i=0;
    unsigned long *table=NULL;
    unsigned int j = 0, min=(1<<16)-1;

    for(j=0;j<v->height;++j)
    {
        if (! viterbi_isEmpty(v,v->width,j))
        {
            metric=viterbi_getMetric(v,v->width,j);
            if ( (metric<minMetric) || (minMetric == -1) ) 
            {
                min=j;
                minMetric=metric; 
            }
        }
    }
    if( (min == (1<<16)-1) || viterbi_isEmpty(v,v->width,min)) {
        ret = 0;
        goto end;
    }

    tmp=viterbi_getAddressPosition(v,v->width,min,0);
    i=v->width;
    table = (unsigned long *) malloc(sizeof(unsigned long)*(v->width));
    if (table == NULL) 
    {
        ret = 0;
        goto end;
    }

    while(tmp && i>0) {
        i--;
        table[i] = position_getInput(tmp);
        tmp=position_getPrevious(tmp);
    }

    *aDataDecoded=int_to_char(table,v->width,pcc_getK(v->aCode),pcc_getN(v->aCode),dataLength);
    if (*aDataDecoded == NULL)
    {
        ret = 0;
        goto end1;
    }

    ret = 1;
end1:
    MY_FREE(table);
end:
    return ret;
}

unsigned char
viterbi_decode( viterbi * v,char ** aDataDecoded, unsigned long *aDataDecodedLength)
{

    /***************/
    unsigned int mN=pcc_getN(v->aCode);
    unsigned int mK=pcc_getK(v->aCode);
    unsigned int mM=pcc_getM(v->aCode);
    unsigned long **mPoly=pcc_getPoly(v->aCode);
    /***************/

    unsigned char ret = 0;
    unsigned char MaxMetric=0;
    unsigned long *nextmemory = NULL;
    unsigned long bitmask=0;
    unsigned long positionNumber = 0;
    unsigned char abit = 0;
    unsigned int input = 0;
    unsigned int value=0;
    unsigned int valueFin=0;
    unsigned long *memory = NULL;
    unsigned long long i = 0;
    unsigned int j=0;
    unsigned char k=0;
    unsigned long m=0;
    unsigned int n_bit=0;
    unsigned char tmpdist=0;

    nextmemory=(unsigned long *) malloc(mK*sizeof(unsigned long));

    if (nextmemory == NULL)
    {
        ret = 0;
        goto end;
    }
    memset(nextmemory,0,sizeof(unsigned long) * mK);
    ret= viterbi_addPosition(v,0,0,0,0,nextmemory,mK,0);
    if (ret == 0)
    {
        ret = 0;
        goto end1;
    }

    for(i=0;i<mM;++i) {
        bitmask <<= 1;
        bitmask |= 1;
    }

    for(i=0;i<v->width;i++) 
    {
        for(j=0;j<v->height;++j) 
        {
            if (viterbi_isEmpty(v,i,j))
                continue;
            positionNumber=viterbi_NumberOfPosition(v,i,j);

            for(m=0;m<positionNumber;m++) 
            {
                memory = viterbi_getMemory(v,i,j,m);

                for(input=0;input<((unsigned int) 1<<mK);++input)
                {
                    valueFin=0;
                    for(k=0;k<mK;++k) {
                        abit=input>>k & 0x1;
                        compute(mN,mM,mPoly[k],abit,memory[k],&value);
                        valueFin^=value;
                        nextmemory[k] = (memory[k]<<1 | abit) & bitmask;
                        value=0;
                    }
                    n_bit=i*mN;
                    tmpdist=distance(v->data,valueFin,mN,n_bit);
                    tmpdist+=viterbi_getMetric(v,i,j);
                    if (tmpdist<=MaxMetric)
                    {
                        ret = viterbi_addPosition(v,i+1,valueFin,tmpdist,input,nextmemory,mK,viterbi_getAddressPosition(v,i,j,m));
                        if (ret == 0)
                        {
                            ret = 0;
                            goto end1;
                        }
                    }
                }
            }
        }
    }

    ret = viterbi_backward(v,aDataDecoded,aDataDecodedLength);
    if (ret == 0)
    {
        ret = 0;
        goto end1;
    }
    ret = 1;
end1:
    MY_FREE(nextmemory);
end:
    return ret;
}
/*****************************************************************************************************/
/*****************************************************************************************************/
/*****************************************************************************************************/
/*****************************************************************************************************/
/*****************************************************************************************************/
/*****************************************************************************************************/
/*                                   End Pcc function                                                */


unsigned char
pcc_Code(
           pcc *aCode,
           const char * data,
           unsigned long length,
           char ** dataCoded,
           unsigned long * codedLength)
{

    /***************/
    unsigned int mN=pcc_getN(aCode);
    unsigned int mK=pcc_getK(aCode);
    unsigned int mM=pcc_getM(aCode);
    unsigned long **mPoly=pcc_getPoly(aCode);
    unsigned long long mX0=pcc_getX0(aCode);
    unsigned char mNumLine=pcc_getNumLine(aCode);
    /***************/

    unsigned char ret = 0;
    unsigned long long i = 0;
    unsigned long j = 0;
    unsigned int k = 0;
    unsigned long * memory=NULL;
    unsigned long bitmask=0;
    unsigned long long binsize;
    char * databin=NULL;
    unsigned int value=0, tmpvalue=0;
    unsigned char abit;
    char *valueFin=NULL;
    char * nstream=0;
    unsigned long  long nsize=0;

    memory= (unsigned long *) malloc(mK*sizeof(unsigned long));

    if (memory == NULL)
    {
        ret = 0;
        goto end;
    }
    memset(memory,0,mK*sizeof(unsigned long));
    
    for(i=0;i<mM;++i) {
        bitmask <<= 1;
        bitmask |= 1;
    }

    ret = char_to_bin(data,length,mK,mN,&databin,&binsize);
    if (ret == 0)
    {
        ret = 0;
        goto end1;
    }
    
    valueFin = (char*)  malloc(sizeof(char)*((binsize*mN/mK)+1));
    if  (valueFin == NULL)
    {
        ret = 0;
        goto end2;
    }
        
    i=0;
    j=0;
    while(i<binsize) {
        for(k=0;k<mK;k++) {
            if(i>=binsize)
               break;
            abit=databin[i++]&0x1; 
            compute(mN,mM,mPoly[k],abit,memory[k],&tmpvalue);
            memory[k]=(memory[k]<<1 | abit)&bitmask;
            value^=tmpvalue;
            tmpvalue=0;
        }
        for(k=0;k<mN;k++) 
            valueFin[j++]=value>>(mN-k-1) &0x1;

        value=0;
    }

    ret = pcc_puncture(aCode,valueFin,j,&nstream,&nsize);
    if (ret == 0)
    {
        ret = 0;
        goto end3;
    }
    
    ret = addNoise(mX0,mNumLine,nstream,nsize);
    if (ret == 0)
    {
        ret = 0;
        goto end4;
    }
    
    dataCoded = bin_to_charHex(nstream,nsize,dataCoded,codedLength);
    if (*dataCoded == NULL)
    {
        ret = 0;
        goto end4;
    }
        
    
end4:
    MY_FREE(nstream);
end3:
    MY_FREE(valueFin);
end2:
    MY_FREE(databin);
end1:
    MY_FREE(memory);
end:
    return ret;
}

unsigned char
pcc_decode(pcc *aCode,
           const char * dataCoded,
           unsigned long length,
           char ** dataDecoded,
           unsigned long * dataLength)
{
    unsigned char ret = 0;
    char * databin = NULL;
    unsigned long long binsize = 0;
    char * dataUnpuctured = NULL;
    unsigned long long unpucturedSize = 0;
    viterbi * v = NULL;

    ret = hex_to_Binchar(dataCoded,length,&databin,&binsize);
    if (ret == 0)
    {
        ret = 0;
        goto end;
    }

    ret = addNoise(pcc_getX0(aCode),pcc_getNumLine(aCode),databin,binsize);
    if (ret == 0)
    {
        ret = 0;
        goto end1;
    }

    ret = pcc_unpuncture(aCode,databin,binsize,&dataUnpuctured,&unpucturedSize);
    if (ret == 0)
    {
        ret = 0;
        goto end1;
    }
    
    v = viterbi_init(aCode,dataUnpuctured,unpucturedSize);
    if (v == NULL)
    {
        ret = 0;
        goto end2;
    }

    ret = viterbi_decode(v,dataDecoded,dataLength);
    if (ret == 0)
    {
        ret = 0;
        goto end3;
    }
    ret = 1;

end3:
    viterbi_delete(v);
    MY_FREE(v);
end2:
    MY_FREE(dataUnpuctured);
end1:
    MY_FREE(databin);
end :
    return ret;
}

void
print_table(char * table, unsigned long size)
{
    unsigned long i;
    perseusLOG(stderr,"Print table, size : %lu\n",size);
    for (i=0;i<size;i++)
    {
        perseusLOG(stderr,"%d ",table[i]);
        fflush(stderr);
    }
    perseusLOG(stderr,"\n");
    fflush(stderr);

}

void 
pcc_debug(pcc* acode)
{
    unsigned int i,j;
    perseusLOG(stderr,"\n /*******CODE PARAMETERS ******/ \n");
    fflush(stderr);
    perseusLOG(stderr,"mN : %u\n",acode->mN);
    fflush(stderr);
    perseusLOG(stderr,"mK : %u\n",acode->mK);
    fflush(stderr);
    perseusLOG(stderr,"mM : %u\n",acode->mM);
    fflush(stderr);
    perseusLOG(stderr,"Poly : ");
    fflush(stderr);
    for (i=0; i< acode->mK;i++) {
        for (j=0; j< acode->mN; j++)
        {
            perseusLOG(stderr," %lu \n",acode->mPoly[i][j]);
            fflush(stderr);
        }
    }
    perseusLOG(stderr,"mMatWidth : %u\n",acode->mMatWidth);
    fflush(stderr);
    perseusLOG(stderr,"mMatDepth : %u\n",acode->mMatDepth);
    fflush(stderr);
    
    perseusLOG(stderr,"Matrix : ");
    fflush(stderr);
    for (i=0; i< acode->mN*acode->mMatWidth;i++)
            perseusLOG(stderr," %u ",acode->mMatrix[i]);

    perseusLOG(stderr,"\nmNumLine : %u\n",acode->mNumLine);
    perseusLOG(stderr,"mX0 : %llu\n",acode->mX0);
    fflush(stderr);

}
void delete_code(pcc *code)
{

    unsigned char i;

    for(i=0;i<code->mK;i++)
    {
        MY_FREE(code->mPoly[i]);
    }
    MY_FREE(code->mPoly);
    MY_FREE(code->mMatrix);
    MY_FREE(code);

}

pcc *
generateCode()
{

    pcc * code = (pcc*) malloc(sizeof(pcc));   
    unsigned int maxPoly;
    unsigned int index;
    unsigned int  nbzero;
    unsigned int i,j,k;

    if (code == NULL)
    {
        goto end;
    }

    srand(time(NULL));

    /*while( (code->mN=rand()% KMAX_GEN) <4);*/
    code->mN = NMIN_GEN + (int) (NMAX_GEN * alea());

    while((code->mK=KMIN_GEN + (int) (KMAX_GEN *alea()))
            >= (code->mN -3)); 

    code->mM= MIN_CONT + (int) (MAX_CONT * alea());

    code->mX0 = (unsigned long long ) rand();
    code->mNumLine = rand() %10;

    /*
     * while ( (code->mMatWidth = rand()% KMAX_GEN) <3);
     */
    /* Generation de la largeur de la Matrice de poinconnage
     * [aPcc->n_output, aPcc->n_output + 5] */
    code->mMatWidth = code->mN + (int)(5.0 * alea() );

    /* Generation aleatoire du poids de la matrice 
     * (n_output* aPcc->PMatrix_Width - nb_zero) 
     * ou nb_zero est aleatoire dans [n_output, 2*n_output] */
    nbzero = (int)((float)(code->mN*code->mMatWidth /8)); ;
    code->mMatDepth = (code->mN*code->mMatWidth) - nbzero;
    /* Allocation de la matrice de poinconnage */
    code->mMatrix = (unsigned char *) 
        malloc(sizeof(unsigned char) *code->mN* code->mMatWidth) ;
    if (code->mMatrix == NULL) 
    {
        goto end1;
    }
    /* Generation de la matrice */
    for(i = 0;i < code->mN* code->mMatWidth;i++) 
        code->mMatrix[i] = 1;

    while (nbzero) {
        index = (int)((float)(code->mMatWidth * code->mN)*alea());
        if (index<=code->mN)
            continue;
        if (code->mMatrix[index]) {
            code->mMatrix[index] = 0;
            nbzero--;
        }
    }
    code->mPoly=(unsigned long **) malloc(sizeof(unsigned long *) * code->mK);
    if(!code->mPoly)
    {
        goto end2;
    }

    maxPoly=(1<<(code->mM+1));

    for(i=0;i<code->mK;i++) {
        code->mPoly[i] =(unsigned long *) malloc(sizeof(unsigned long) * code->mN);
        if (code->mPoly[i] == NULL) {
            for(k=0;k<i;k++)
            {
                MY_FREE(code->mPoly[k]);
            }
            goto end3;
        }
        for(j=0;j<code->mN;j++)
            while( (code->mPoly[i][j]=rand() % maxPoly) <1);
    }
    goto end;

end3:
    MY_FREE(code->mPoly);
end2:
    MY_FREE(code->mMatrix);
end1:
    MY_FREE(code);
end:
    return code;
}
